/*
 *   C++ sockets on Unix and Windows
 *   Copyright (C) 2002
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef PRACTICALSOCKET_INCLUDED
#define PRACTICALSOCKET_INCLUDED

#include <string>            // For string
#include <exception>         // For exception class


namespace jell {



/**
 *   Signals a problem with the execution of a socket call.
 */
class SocketException : public std::exception
{
    public:
        /**
         *   Construct a SocketException with a explanatory message.
         *   @param message explanatory message
         *   @param incSysMsg true if system message (from strerror(errno))
         *   should be postfixed to the user provided message
         */
        SocketException(const std::string &message, bool appendSystemErrorMessage = false) throw();

        /**
         *   Provided just to guarantee that no exceptions are thrown.
         */
        ~SocketException() throw();

        /**
         *   Get the exception message
         *   @return exception message
         */
        const char *what() const throw();

    private:
        std::string userMessage;  // Exception message
};

/**
 *   Base class representing basic communication endpoint
 */
class Socket {
    public :
        typedef int     socket_t;

    public:
        /**
         *   Close and deallocate this socket
         */
        ~Socket();

        /**
         *   Get the local address
         *   @return local address of socket
         *   @exception SocketException thrown if fetch fails
         */
        std::string getLocalAddress() throw(SocketException);

        /**
         *   Get the local port
         *   @return local port of socket
         *   @exception SocketException thrown if fetch fails
         */
        unsigned short getLocalPort() throw(SocketException);

        /**
         *   Set the local port to the specified port and the local address
         *   to any interface
         *   @param localPort local port
         *   @exception SocketException thrown if setting local port fails
         */
        void setLocalPort(unsigned short localPort) throw(SocketException);

        /**
         *   Set the local port to the specified port and the local address
         *   to the specified address.  If you omit the port, a random port
         *   will be selected.
         *   @param localAddress local address
         *   @param localPort local port
         *   @exception SocketException thrown if setting local port or address fails
         */
        void setLocalAddressAndPort(const std::string &localAddress,
                unsigned short localPort = 0) throw(SocketException);

        /**
         *   If WinSock, unload the WinSock DLLs; otherwise do nothing.  We ignore
         *   this in our sample client code but include it in the library for
         *   completeness.  If you are running on Windows and you are concerned
         *   about DLL resource consumption, call this after you are done with all
         *   Socket instances.  If you execute this on Windows while some instance of
         *   Socket exists, you are toast.  For portability of client code, this is
         *   an empty function on non-Windows platforms so you can always include it.
         *   @param buffer buffer to receive the data
         *   @param bufferLen maximum number of bytes to read into buffer
         *   @return number of bytes read, 0 for EOF, and -1 for error
         *   @exception SocketException thrown WinSock clean up fails
         */
        static void cleanUp() throw(SocketException);

        /**
         *   Resolve the specified service for the specified protocol to the
         *   corresponding port number in host byte order
         *   @param service service to resolve (e.g., "http")
         *   @param protocol protocol of service to resolve.  Default is "tcp".
         */
        static unsigned short resolveService(const std::string &service,
                const std::string &protocol = "tcp");

    public :
        /**
         *   return file-discriptor
         */
        int    descriptor(void) const;

    public :
        // options setting
        void    setSendTimeout(unsigned microseconds) throw(SocketException);
        void    setRecvTimeout(unsigned microseconds) throw(SocketException);

        void    setSendBufSize(unsigned size) throw(SocketException);
        void    setRecvBufSize(unsigned size) throw(SocketException);

    private:
        // Prevent the user from trying to use value semantics on this object
        Socket(const Socket &sock);
        void operator=(const Socket &sock);

    protected:
        socket_t sockDesc;              // Socket descriptor
        Socket(int type, int protocol) throw(SocketException);
        Socket(socket_t sockDesc);
};

/**
 *   Socket which is able to connect, send, and receive
 */
class TransportSocket : public Socket
{
    public:
        /**
         *   Establish a socket connection with the given foreign
         *   address and port
         *   @param foreignAddress foreign address (IP address or name)
         *   @param foreignPort foreign port
         *   @exception SocketException thrown if unable to establish connection
         */
        void connect(const std::string &foreignAddress, unsigned short foreignPort)
            throw(SocketException);

        /**
         *   Write the given buffer to this socket.  Call connect() before
         *   calling send()
         *   @param buffer buffer to be written
         *   @param bufferLen number of bytes from buffer to be written
         *   @return number of bytes written, 0 for EOF, and -1 for error
         *   @exception SocketException thrown if unable to send data
         */
        int send(const void *buffer, int bufferLen) throw(SocketException);

        void sendn(const void *buffer, int sizeToSend, int timeout=-1 /*in seconds*/) throw(SocketException);

        /**
         *   Read into the given buffer up to bufferLen bytes data from this
         *   socket.  Call connect() before calling recv()
         *   @param buffer buffer to receive the data
         *   @param bufferLen maximum number of bytes to read into buffer
         *   @return number of bytes read, 0 for EOF, and -1 for error
         *   @exception SocketException thrown if unable to receive data
         */
        int recv(void *buffer, int bufferLen) throw(SocketException);

        int recvn(void *buffer, int sizeToReceive, int timeout=-1 /*in seconds*/ ) throw(SocketException);

        /**
         *   Get the foreign address.
         *   @return foreign address
         *   @exception SocketException thrown if unable to fetch foreign address
         */
        std::string getForeignAddress() throw(SocketException);

        /**
         *   Get the foreign port.
         *   @return foreign port
         *   @exception SocketException thrown if unable to fetch foreign port
         */
        unsigned short getForeignPort() throw(SocketException);

    protected:
        TransportSocket(int type, int protocol) throw(SocketException);
        TransportSocket(int newConnSD);
};

/**
 *   TCP socket for communication with other TCP sockets
 */
class TCPSocket : public TransportSocket
{
    public:
        /**
         *   Construct a TCP socket with no connection
         *   @exception SocketException thrown if unable to create TCP socket
         */
        TCPSocket() throw(SocketException);

        /**
         *   Construct a TCP socket with a connection to the given foreign address
         *   and port
         *   @param foreignAddress foreign address (IP address or name)
         *   @param foreignPort foreign port
         *   @exception SocketException thrown if unable to create TCP socket
         */
        TCPSocket(const std::string &foreignAddress, unsigned short foreignPort)
            throw(SocketException);

    private:
        // Access for TCPServerSocket::accept() connection creation
        friend class TCPServerSocket;
        TCPSocket(int newConnSD);
};

/**
 *   TCP socket class for servers
 */
class TCPServerSocket : public Socket
{
    public:
        /**
         *   Construct a TCP socket for use with a server, accepting connections
         *   on the specified port on any interface
         *   @param localPort local port of server socket, a value of zero will
         *                   give a system-assigned unused port
         *   @param queueLen maximum queue length for outstanding
         *                   connection requests (default 5)
         *   @exception SocketException thrown if unable to create TCP server socket
         */
        TCPServerSocket(unsigned short localPort, int queueLen = 5)
            throw(SocketException);

        /**
         *   Construct a TCP socket for use with a server, accepting connections
         *   on the specified port on the interface specified by the given address
         *   @param localAddress local interface (address) of server socket
         *   @param localPort local port of server socket
         *   @param queueLen maximum queue length for outstanding
         *                   connection requests (default 5)
         *   @exception SocketException thrown if unable to create TCP server socket
         */
        TCPServerSocket(const std::string &localAddress, unsigned short localPort,
                int queueLen = 5) throw(SocketException);

        /**
         *   Blocks until a new connection is established on this socket or error
         *   @return new connection socket
         *   @exception SocketException thrown if attempt to accept a new connection fails
         */
        TCPSocket *accept() throw(SocketException);

    private:
        void setListen(int queueLen) throw(SocketException);
};

/**
 *   UDP socket class
 */
class UDPSocket : public TransportSocket
{
    public:
        /**
         *   Construct a UDP socket
         *   @exception SocketException thrown if unable to create UDP socket
         */
        UDPSocket() throw(SocketException);

        /**
         *   Construct a UDP socket with the given local port
         *   @param localPort local port
         *   @exception SocketException thrown if unable to create UDP socket
         */
        UDPSocket(unsigned short localPort) throw(SocketException);

        /**
         *   Construct a UDP socket with the given local port and address
         *   @param localAddress local address
         *   @param localPort local port
         *   @exception SocketException thrown if unable to create UDP socket
         */
        UDPSocket(const std::string &localAddress, unsigned short localPort)
            throw(SocketException);

        /**
         *   Unset foreign address and port
         *   @return true if disassociation is successful
         *   @exception SocketException thrown if unable to disconnect UDP socket
         */
        void disconnect() throw(SocketException);

        /**
         *   Send the given buffer as a UDP datagram to the
         *   specified address/port
         *   @param buffer buffer to be written
         *   @param bufferLen number of bytes to write
         *   @param foreignAddress address (IP address or name) to send to
         *   @param foreignPort port number to send to
         *   @return true if send is successful
         *   @exception SocketException thrown if unable to send datagram
         */
        void sendTo(const void *buffer, int bufferLen, const std::string &foreignAddress,
                unsigned short foreignPort) throw(SocketException);

        /**
         *   Read read up to bufferLen bytes data from this socket.  The given buffer
         *   is where the data will be placed
         *   @param buffer buffer to receive data
         *   @param bufferLen maximum number of bytes to receive
         *   @param sourceAddress address of datagram source
         *   @param sourcePort port of data source
         *   @return number of bytes received and -1 for error
         *   @exception SocketException thrown if unable to receive datagram
         */
        int recvFrom(void *buffer, int bufferLen, std::string &sourceAddress,
                unsigned short &sourcePort) throw(SocketException);

        /**
         *   Set the multicast TTL
         *   @param multicastTTL multicast TTL
         *   @exception SocketException thrown if unable to set TTL
         */
        void setMulticastTTL(unsigned char multicastTTL) throw(SocketException);

        /**
         *   Join the specified multicast group
         *   @param multicastGroup multicast group address to join
         *   @exception SocketException thrown if unable to join group
         */
        void joinGroup(const std::string &multicastGroup) throw(SocketException);

        /**
         *   Leave the specified multicast group
         *   @param multicastGroup multicast group address to leave
         *   @exception SocketException thrown if unable to leave group
         */
        void leaveGroup(const std::string &multicastGroup) throw(SocketException);

    private:
        void setBroadcast();
};


}   // namespace jell

#endif

